#include "config.h"

#include <gtest/gtest.h>

#include <cstdio>
#include <string>

using namespace cfg;

// 测试数据文件路径辅助：返回当前测试源文件所在目录的 data/filename
static std::string
test_data_path(const char * filename)
{
    std::string file = __FILE__;
    auto pos = file.find_last_of("/\\");
    std::string dir = (pos == std::string::npos) ? std::string(".") : file.substr(0, pos);
    return dir + "/data/" + filename;
}

// include_file：对象根合并
TEST(IncludeFileTest, IncludeFile_ObjectRootMerge)
{
#ifndef CFG_ENABLE_YAML
    Builder b;
    b.include_file(test_data_path("include_sample.yaml"));
    auto fr = b.freeze();
    ASSERT_FALSE(fr.has_value());
#else
    Builder b;
    b.include_file(test_data_path("include_sample.yaml"));
    auto fr = b.freeze();
    ASSERT_TRUE(fr.has_value());

    View v(*fr);
    // 验证对象键合并
    auto a = v.lookup("a");
    ASSERT_TRUE(a.has_value());
    auto ai = a->get_int64();
    ASSERT_TRUE(ai.has_value());
    EXPECT_EQ(*ai, 1);

    auto host = v.lookup("db.host");
    ASSERT_TRUE(host.has_value());
    auto hs = host->get_string();
    ASSERT_TRUE(hs.has_value());
    EXPECT_EQ(*hs, "localhost");

    auto port = v.lookup("db.port");
    ASSERT_TRUE(port.has_value());
    auto pi = port->get_int64();
    ASSERT_TRUE(pi.has_value());
    EXPECT_EQ(*pi, 5432);

    auto x = v.lookup("arr[2].x");
    ASSERT_TRUE(x.has_value());
    auto xs = x->get_string();
    ASSERT_TRUE(xs.has_value());
    EXPECT_EQ(*xs, "ok");
#endif
}

// include_file：非对象根兜底 value 键
TEST(IncludeFileTest, IncludeFile_NonObjectRootFallback)
{
#ifndef CFG_ENABLE_YAML
    Builder b;
    b.include_file(test_data_path("include_value.json"));
    auto fr = b.freeze();
    ASSERT_FALSE(fr.has_value());
#else
    Builder b;
    b.include_file(test_data_path("include_value.json"));
    auto fr = b.freeze();
    ASSERT_TRUE(fr.has_value());

    View v(*fr);
    // 非对象根应兜底写入 key "value"
    auto val = v.lookup("value");
    ASSERT_TRUE(val.has_value());
    auto vi = val->get_int64();
    ASSERT_TRUE(vi.has_value());
    EXPECT_EQ(*vi, 42);
#endif
}

// include_file：文件不存在与解析错误
TEST(IncludeFileTest, IncludeFile_FileNotFoundAndParseError)
{
#ifndef CFG_ENABLE_YAML
    Builder b;
    b.include_file(test_data_path("not_exist.yaml"));
    auto fr = b.freeze();
    ASSERT_FALSE(fr.has_value());
#else
    // 文件不存在
    {
        Builder b;
        b.include_file(test_data_path("not_exist.yaml"));
        auto fr = b.freeze();
        ASSERT_FALSE(fr.has_value());
        bool has_open_error = false;
        for (const auto & e : fr.error())
        {
            if (e.message.find("cannot open file") != std::string::npos)
            {
                has_open_error = true;
                break;
            }
        }
        EXPECT_TRUE(has_open_error);
    }

    // 解析错误：构造一个非法 YAML 内容文件
    {
        const std::string bad_path = test_data_path("bad.yaml");
        // 在运行测试前创建坏文件（使用 C 接口以降低对标准库实现的依赖）
        {
            FILE * fp = std::fopen(bad_path.c_str(), "w");
            ASSERT_NE(fp, nullptr);
            std::fputs("a: [1, 2\n", fp); // 缺失右方括号
            std::fclose(fp);
        }

        Builder b;
        b.include_file(bad_path);
        auto fr = b.freeze();
        ASSERT_FALSE(fr.has_value());
        bool has_parse_error = false;
        for (const auto & e : fr.error())
        {
            if (e.message.find("parse error") != std::string::npos)
            {
                has_parse_error = true;
                break;
            }
        }
        EXPECT_TRUE(has_parse_error);
    }
#endif
}

// include 路径解析模式：相对路径基于 base_dir 解析
// 该用例仅在启用 YAML 时运行。
TEST(IncludeFileTest, IncludeFile_PathBaseMode)
{
#ifndef CFG_ENABLE_YAML
    GTEST_SKIP() << "CFG_ENABLE_YAML not enabled";
#else
    // 计算 data 目录作为基准目录
    // test_data_path("") 返回形如 "/.../tests/data/"，可直接作为 base dir 使用
    const std::string base_dir = test_data_path("");

    Builder b;
    b.set_include_path_mode(IncludePathMode::RelativeToBaseDir).set_include_base_dir(base_dir);

    // 使用相对路径（仅文件名），应相对 base_dir 解析
    b.include_file("include_sample.yaml");
    auto fr = b.freeze();
    ASSERT_TRUE(fr.has_value());

    View v(*fr);
    auto a = v.lookup("a");
    ASSERT_TRUE(a.has_value());
    auto ai = a->get_int64();
    ASSERT_TRUE(ai.has_value());
    EXPECT_EQ(*ai, 1);
#endif
}